package extensions

import (
	"context"
	"mediumkube/pkg/cni"
	"mediumkube/pkg/common"
	"mediumkube/pkg/configurations"
	"mediumkube/pkg/cri"
	"mediumkube/pkg/network"
	"mediumkube/pkg/utils"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/namespaces"
	"k8s.io/klog/v2"
)

const (
	// containerd namespace, not linux namespace
	ns = "ns-mediumkube"

	label_key_IP  = "cri-ip-addr"
	label_key_Msg = "cri-msg"
)

func client() *containerd.Client {

	c, err := cri.NewContainerdClient()
	if err != nil {
		klog.Error(err)
	}
	return c
}

func RunExtensions(ctx context.Context) {
	config := configurations.Config()
	cleanUpExtension(config.Extensions)
	for _, ext := range config.Extensions {
		klog.Info("Launching extension: ", ext.Name)
		go runExtensions(&ext)
	}
	<-ctx.Done()
}

func ListExtensions(ctx context.Context) []common.ExtensionVO {
	var c *containerd.Client
	if c = client(); c == nil {
		return nil
	}
	config := configurations.Config()
	res := make([]common.ExtensionVO, len(config.Extensions))
	criCtx := namespaces.WithNamespace(context.Background(), ns)
	for i, ext := range config.Extensions {
		container, err := c.ContainerService().Get(criCtx, ext.Name)
		res[i] = common.ExtensionVO{
			Name:  ext.Name,
			Image: ext.Image,
			Port:  ext.Port,
		}

		if err != nil {
			res[i].Message = err.Error()
		} else {
			labels := container.Labels
			klog.Infof("Labels: %v", labels)
			ip, ok := labels[label_key_IP]
			if ok {
				res[i].IP = ip
			}
			msg, ok := labels[label_key_Msg]
			if ok {
				res[i].Message = msg
			}
		}
	}
	return res
}

func CleanUpExtensions() {
	config := configurations.Config()
	extVos := ListExtensions(context.Background())
	for _, v := range extVos {
		if len(v.IP) > 0 {
			network.RemoveMapping(utils.GetIPFromCidr(config.Bridge.Inet), v.IP, v.Port, v.Port)

		}
	}
	cleanUpExtension(config.Extensions)
}

func cleanUpExtension(extensions []common.Extension) {
	klog.Info("Cleaning up extensions")
	var c *containerd.Client
	if c = client(); c == nil {
		return
	}
	ctx := namespaces.WithNamespace(context.Background(), ns)

	err := cri.DeleteContainers(c, ns)
	if err != nil {
		klog.Warning("Failed to clean up containers")
	}
	for _, ext := range extensions {
		cni.CleanUPCNI(ctx, ext.NetNS(), ext)
		if err != nil {
			klog.Error("Failed to cleanup snapshot", err)
		}
	}

}

// Containerd specification
// https://containerd.io/docs/getting-started/#starting-containerd

// CNI specification
// https://github.com/containernetworking/cni

// Golang integration for cni
// https://github.com/containerd/go-cni
func runExtensions(ext *common.Extension) {
	ctx := namespaces.WithNamespace(context.Background(), ns)

	var c *containerd.Client
	if c = client(); c == nil {
		return
	}

	klog.Info("Applying extension ", ext.Name)

	container, err := cri.CreateContainer(
		ctx, c, ext.Name, ext.Image, ext.SnapshotID(), ext.NetNS(), ext.Args...,
	)
	if err != nil {
		klog.Error("Failed to apply extension: ", ext.Name, "Err: ", err)
		return
	}
	labels := make(map[string]string)

	res, err := cni.ApplyBridgePlugin(ctx, ext.NetNS(), container, *ext, nil)
	config := configurations.Config()

	if err != nil {
		klog.Error("Failed to apply cni plugin for extension: ", ext.Name, "Err: ", err)
		labels[label_key_Msg] = err.Error()
	} else {
		raw := res.Raw()
		if len(raw) > 0 {
			if ips := raw[0].IPs; len(ips) > 0 {
				ip := ips[0].Address.IP
				labels[label_key_IP] = ip.String()
			}
		}

	}
	if ip, ok := labels[label_key_IP]; ok {
		// Apply DNAT
		network.MapPort(utils.GetIPFromCidr(config.Bridge.Inet), ip, ext.Port, ext.Port)
	}
	_, err = container.SetLabels(ctx, labels)
	if err != nil {
		klog.Errorf("Error setting labels: %v", err)
	}

	cri.LaunchContainer(ns, container)

}
